package model.mapping;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Hashtable;

import view.panels.MapPanel;
import model.Model;
import model.info.Constants;
import model.info.Coordinate;
import model.info.CoordinateRangeException;
import model.info.MapNode;
import model.info.MapWay;
import model.info.OSMDecoder;

public class ModelMapBuilder {

	private MapPoint[][] driverMap;
	private Model model;
	private Coordinate first;
	private Coordinate second;
	private ArrayList<Point> freePoints;
	private ArrayList<MapPoint> crossings;
	private OSMDecoder decoder;
	private int pair;
	
	public ModelMapBuilder(Model model) {
		
		this.model = model;
		freePoints = new ArrayList<Point>();
		crossings = new ArrayList<MapPoint>();
		decoder = new OSMDecoder();
		pair = 0;
	}
	
	public Coordinate getFirst() {
		return first;
	}

	public void setFirst(Coordinate first) {
		this.first = first;
	}

	public Coordinate getSecond() {
		return second;
	}

	public void setSecond(Coordinate second) {
		this.second = second;
	}
	
	public ArrayList<Point> getFreePoints() {
		return freePoints;
	}

	public void setFreePoints(ArrayList<Point> freePoints) {
		this.freePoints = freePoints;
	}
	
	public ArrayList<MapPoint> getCrossings() {
		return crossings;
	}

	public void setCrossings(ArrayList<MapPoint> crossings) {
		this.crossings = crossings;
	}

	public OSMDecoder getCodificador() {
		return decoder;
	}

	public void setCodificador(OSMDecoder codificador) {
		this.decoder = codificador;
	}
	
	public MapPoint[][] getDriverMap() {
		return driverMap;
	}

	public void setDriverMap(MapPoint[][] mapaModelo) {
		this.driverMap = mapaModelo;
	}
	
	public void configureModelMap() {
		
		freePoints.clear();
		crossings.clear();
		MapPanel mapaPanel = model.getMapPanel();
		driverMap = new MapPoint[mapaPanel.getWidth()][mapaPanel.getHeight()];
		
		//pedestrianMap = new MapPoint[mapaPanel.getWidth()][mapaPanel.getHeight()];
		//generalInfoMap = new MapPoint[mapaPanel.getWidth()][mapaPanel.getHeight()];
		
		for(int i=0;i<mapaPanel.getWidth();i++)
			for (int j=0;j<mapaPanel.getHeight();j++) {
				driverMap[i][j] = new MapPoint(false,i,j);

				//pedestrianMap[i][j] = new MapPoint(false);
				//generalInfoMap[i][j] = new MapPoint(false);
			}
		buildRectangleInfo();
	}
	
	public void getOpenStreetMapInfo() {
		
		Double left = first.getLongitude();
		Double down = second.getLatitude();
		Double right = second.getLongitude();
		Double up = first.getLatitude();
		decoder.getOnLineInfo(left,down,right,up);
	}

	public void buildRectangleInfo() {
	
		MapPanel mapPanel = model.getMapPanel();
		double lat1 = Double.valueOf(MapPanel.format(MapPanel.position2lat
			(mapPanel.getMapPosition().y,mapPanel.getZoom())));
		double lon1 = Double.valueOf(MapPanel.format(MapPanel.position2lon
			(mapPanel.getMapPosition().x,mapPanel.getZoom())));
		double lat2 = Double.valueOf(MapPanel.format(MapPanel.position2lat
				(mapPanel.getMapPosition().y+mapPanel.getHeight()-1,mapPanel.getZoom())));
		double lon2 = Double.valueOf(MapPanel.format(MapPanel.position2lon
			(mapPanel.getMapPosition().x+mapPanel.getWidth()-1,mapPanel.getZoom())));
		try {
			first = new Coordinate(lat1,lon1);
			second = new Coordinate(lat2,lon2);
		} 
		catch (CoordinateRangeException e) {
			e.printStackTrace();
		}
	}
	
	public void generateMap() {

		MapPanel mapPanel = model.getMapPanel();
		BufferedImage driverBuffer = new BufferedImage(mapPanel.getWidth(),mapPanel.getHeight(),BufferedImage.TYPE_INT_RGB);
		//BufferedImage pedestrianBuffer = new BufferedImage(mapaPanel.getWidth(),mapaPanel.getHeight(),BufferedImage.TYPE_INT_RGB);
		//BufferedImage generalBuffer = new BufferedImage(mapaPanel.getWidth(),mapaPanel.getHeight(),BufferedImage.TYPE_INT_RGB);
		
		for (int i=0;i<decoder.getWayList().size();i++) {
			MapWay way = decoder.getWayList().get(i);
			Hashtable<String,String> info = way.getData();
			String id = way.getId();
			Boolean oneway = getOneWay(info);
			Integer lanes = getNumLanes(info);
			Integer value = getMapType(info);
			Integer layer = getLayer(info);
			MapPoint[][] map = driverMap;
			BufferedImage image = driverBuffer;
			pair = 0;
			for (int j=0;j<way.getNodes().size()-1;j++) {
				Coordinate coordinate1 = way.getNodes().get(j).getCoordinate();
				Coordinate coordinate2 = way.getNodes().get(j+1).getCoordinate();
				Integer x1 = MapPanel.lon2position(coordinate1.getLongitude(),mapPanel.getZoom());
				Integer y1 = MapPanel.lat2position(coordinate1.getLatitude(),mapPanel.getZoom());
				
				Integer x2 = MapPanel.lon2position(coordinate2.getLongitude(),mapPanel.getZoom());
				Integer y2 = MapPanel.lat2position(coordinate2.getLatitude(),mapPanel.getZoom());
				
				//Obtenemos distancia al centro del mapa.
				x1 = x1 - mapPanel.getCenterPosition().x;
				y1 = y1 - mapPanel.getCenterPosition().y;
				
				//Obtenemos el punto exacto del mapa.
				Integer xf1 = mapPanel.getWidth()/2 + x1;
				Integer yf1 = mapPanel.getHeight()/2 + y1;
				
				x2 = x2 - mapPanel.getCenterPosition().x;
				y2 = y2 - mapPanel.getCenterPosition().y;
				
				Integer xf2 = mapPanel.getWidth()/2 + x2;
				Integer yf2 = mapPanel.getHeight()/2 + y2;
				
				if (value == 1)	{
				Graphics2D g2 = image.createGraphics();
				g2.setColor(Color.white);
				g2.setStroke(new BasicStroke(1.0f));
				way.getNodes().get(j).setPoint(new Point(xf1,yf1));
				way.getNodes().get(j+1).setPoint(new Point(xf2,yf2));
				Integer direction = model.getMapCheckouts().getDirection(xf1,yf1,xf2,yf2,true);
				int currentPair = pair;
				if (oneway) {
					for (int p=lanes;p>=1;p--) {
						pair = currentPair;
						Line2D l = new Line2D.Float(xf1,yf1,xf2,yf2);
						g2.draw(l);
						translateInfoModelo(map,image,info,id,oneway,direction,lanes,p,true,xf1,yf1,xf2,yf2);
						ArrayList<Point> list = buildNextRoad(xf1,yf1,xf2,yf2);
						xf1 = list.get(0).x;
						yf1 = list.get(0).y;
						xf2 = list.get(1).x;
						yf2 = list.get(1).y;
					}
				}
				else if (!oneway) {
					Line2D l = new Line2D.Float(xf1,yf1,xf2,yf2);
					g2.draw(l);
					currentPair = pair;
					translateInfoModelo(map,image,info,id,oneway,direction,lanes,lanes,true,xf1,yf1,xf2,yf2);
					ArrayList<Point> list = buildOppositeRoad(xf1,yf1,xf2,yf2);
					int xff1 = list.get(0).x;
					int yff1 = list.get(0).y;
					int xff2 = list.get(1).x;
					int yff2 = list.get(1).y;
					l = new Line2D.Float(xff1,yff1,xff2,yff2);
					g2.draw(l);
					pair = currentPair;
					translateInfoModelo(map,image,info,id,oneway,model.getMapCheckouts().getOpposedDirection(direction),
						lanes,lanes,false,xff1,yff1,xff2,yff2);
						
					//int p = 2;
					for (int p=lanes-1;p>=1;p--) {
						list.clear();
						list = buildNextRoad(xf1,yf1,xf2,yf2);
						xf1 = list.get(0).x;
						yf1 = list.get(0).y;
						xf2 = list.get(1).x;
						yf2 = list.get(1).y;
						l = new Line2D.Float(xf1,yf1,xf2,yf2);
						g2.draw(l);
						pair = currentPair;
						translateInfoModelo(map,image,info,id,oneway,direction,lanes,p,true,xf1,yf1,xf2,yf2);
								
						list.clear();
						list = buildOppositeRoad(xff1,yff1,xff2,yff2);
						xff1 = list.get(0).x;
						yff1 = list.get(0).y;
						xff2 = list.get(1).x;
						yff2 = list.get(1).y;
						l = new Line2D.Float(xff1,yff1,xff2,yff2);
						g2.draw(l);
						pair = currentPair;
						translateInfoModelo(map,image,info,id,oneway,model.getMapCheckouts().getOpposedDirection(direction),
							lanes,p,false,xff1,yff1,xff2,yff2);
					}
				}
				}
			}
		}
	model.getMapCheckouts().pruebaDeFuncionamiento(driverMap,driverBuffer);
	}
	
	public void updateFreePoints() {
		
		for (int i=0;i<freePoints.size();i++) {
			int x = freePoints.get(i).x;
			int y = freePoints.get(i).y;
			for (int j=0;j<driverMap[x][y].getDataList().size();j++) {
				if (driverMap[x][y].getDataList().get(j).getDirection().equals(-1)) {
					Boolean found = false;
					for (int k=x-1;k<=x+1&&!found;k++)
						for (int l=y-1;l<=y+1&&!found;l++)
							if (model.getMapCheckouts().isInMap(k,l) && driverMap[k][l].isRoad()) {
								found = true;
								driverMap[x][y].setRoad(true);
								//driverMap[x][y].setOcupante(Individuals.Vehiculo.ordinal()); //comprobacion
								driverMap[x][y].getDataList().get(j).setDirection
								(driverMap[k][l].getDataList().get(0).getDirection());
						}
				}
			}
		}
	}
	
	public void putCoordinates() {
		
		MapPanel mapPanel = model.getMapPanel();
		for(int i=0;i<mapPanel.getWidth();i++)
			for (int j=0;j<mapPanel.getHeight();j++) {
				if (driverMap[i][j].isRoad()) {
					double lat = Double.valueOf(MapPanel.format(MapPanel.position2lat
							(mapPanel.getMapPosition().y+j,mapPanel.getZoom())));
					double lon = Double.valueOf(MapPanel.format(MapPanel.position2lon
							(mapPanel.getMapPosition().x+i,mapPanel.getZoom())));
					Coordinate coordinate = null;
					try {
						coordinate = new Coordinate(lat,lon);
					} catch (CoordinateRangeException e) {
						e.printStackTrace();
					}
					driverMap[i][j].setCoordinate(coordinate);
				}
			}
	}
	
	public Boolean getOneWay(Hashtable<String,String> info) {
		
		Boolean oneway = false;
		String value = "";
		if (info.containsKey(Constants.ONEWAY)) {
			value = info.get(Constants.ONEWAY);
			if (value.equals(Constants.YES) || value.equals(Constants.UNO))
				oneway = true;
		}
		else if (info.containsKey(Constants.JUNCTION)) {
			value = info.get(Constants.JUNCTION);
			if (value.equals(Constants.ROUNDABOUT))
				oneway = true;
		}
		else if (info.containsKey(Constants.HIGHWAY)) {
			value = info.get(Constants.HIGHWAY);
			if (value.equals(Constants.MOTORWAY))
				oneway = true;
		}
		return oneway;
	}

	public Integer getNumLanes(Hashtable<String,String> info) {
		
		Integer lanes = 1;
		if (info.containsKey(Constants.LANES)) {
			String value = info.get(Constants.LANES);
			lanes = new Integer(value);
		}
		return lanes;
	}
	
	public Integer getLayer(Hashtable<String,String> info) {
		
		Integer layer = 0;
		if (info.containsKey(Constants.LAYER)) {
			String value = info.get(Constants.LAYER);
			layer = new Integer(value);
		}
		return layer;
	}
	
	public ArrayList<Point> buildNextRoad(int x1,int y1,int x2,int y2) {
		
		int rx = x2 - x1;
        int ry = y2 - y1;
     
        if (rx > 0 && ry > 0) {
        	x1 = x1-1;
        	y1 = y1+1;
        	x2 = x2-1;
        	y2 = y2+1;
        }
        else if (rx > 0 && ry == 0) {
        	y1 = y1+1;
        	y2 = y2+1;
        }
        else if (rx > 0 && ry < 0) {
        	x1 = x1+1;
        	y1 = y1+1;
        	x2 = x2+1;
        	y2 = y2+1;
        }
        else if (rx < 0 && ry > 0) {
        	x1 = x1-1;
        	y1 = y1-1;
        	x2 = x2-1;
        	y2 = y2-1;
        }
        else if (rx < 0 && ry == 0) {
        	y1 = y1-1;
        	y2 = y2-1;
        }
        else if (rx < 0 && ry < 0) {
        	x1 = x1+1;
        	y1 = y1-1;
        	x2 = x2+1;
        	y2 = y2-1;
        }
        else if (rx == 0 && ry > 0) {
        	x1 = x1-1;
        	x2 = x2-1;
        }
        else if (rx == 0 && ry < 0) {
        	x1 = x1+1;
        	x2 = x2+1;
        }
      ArrayList<Point> list = new ArrayList<Point>();
      Point p1 = new Point(x1,y1);
      Point p2 = new Point(x2,y2);
      list.add(p1);
      list.add(p2);
      return list;
	}
	
	public  ArrayList<Point> buildOppositeRoad(int x1,int y1,int x2,int y2) {
		
		int rx = x2 - x1;
        int ry = y2 - y1;
     
        if (rx > 0 && ry > 0) {
        	x1 = x1+1;
        	y1 = y1-1;
        	x2 = x2+1;
        	y2 = y2-1;
        }
        else if (rx > 0 && ry == 0) {
        	y1 = y1-1;
        	y2 = y2-1;
        }
        else if (rx > 0 && ry < 0) {
        	x1 = x1-1;
        	x2 = x2-1;
        	y1 = y1-1;
        	y2 = y2-1;
        }
        else if (rx < 0 && ry > 0) {
        	x1 = x1+1;
        	x2 = x2+1;
        	y1 = y1+1;
        	y2 = y2+1;
        }
        else if (rx < 0 && ry == 0) {
        	y1 = y1+1;
        	y2 = y2+1;
        }
        else if (rx < 0 && ry < 0) {
        	x1 = x1-1;
        	y1 = y1+1;
        	x2 = x2-1;
        	y2 = y2+1;
        }
        else if (rx == 0 && ry > 0) {
        	x1 = x1+1;
        	x2 = x2+1;
        }
        else if (rx == 0 && ry < 0) {
        	x1 = x1-1;
        	x2 = x2-1;
        }
        
        ArrayList<Point> list = new ArrayList<Point>();
        Point p1 = new Point(x1,y1);
        Point p2 = new Point(x2,y2);
        list.add(p1);
        list.add(p2);
        return list;
	}
	
	public Integer getMapType(Hashtable<String,String> info) {
		
		Integer result = 0;
		String value = "";
		if (info.containsKey(Constants.HIGHWAY)) {
			value = info.get(Constants.HIGHWAY);
			if (model.getMapCheckouts().isVehicleWay(value))
				result = 1;
			else
				result = 2;
		}
		else
			result = 3;
		return result;
	}
	
	public void translateInfoModelo(MapPoint[][] mapa,BufferedImage buffer,Hashtable<String,String> wayInfo,
			String id,Boolean onew,Integer direction,Integer lanes,Integer numLane,Boolean side,Integer x1,Integer y1,
			Integer x2,Integer y2) {
		
		Integer layer = getLayer(wayInfo);
		String type = "";
		String name = "";
		Boolean oneway = onew;
		
		Integer sentido = direction;
		if (wayInfo.containsKey(Constants.HIGHWAY))
			type = wayInfo.get(Constants.HIGHWAY);
		if (wayInfo.containsKey(Constants.NAME))
			name = wayInfo.get(Constants.NAME);
		Integer rx = (x2 - x1);
		Integer ry = (y2 - y1);
		
		if (rx >= 0 && ry >= 0) {
			for (int i=x1;i<=x2;i++)
				for (int j=y1;j<=y2;j++)
					if (model.getMapCheckouts().isInMap(i,j) && buffer.getRGB(i,j) == Color.white.getRGB()) {
							putInfo(mapa,id,name,type,sentido,lanes,numLane,oneway,layer,side,i,j);
							buffer.setRGB(i,j,new Color(0,0,0).getRGB());
							pair = pair + 1;
					}
		}
		else if (rx < 0 && ry >= 0) {
			for (int i=x2;i<=x1;i++)
				for (int j=y1;j<=y2;j++)
					if (model.getMapCheckouts().isInMap(i,j) && buffer.getRGB(i,j) == Color.white.getRGB()) {
						putInfo(mapa,id,name,type,sentido,lanes,numLane,oneway,layer,side,i,j);	
						buffer.setRGB(i,j,new Color(0,0,0).getRGB());
						pair = pair + 1;
					}
		}
		else if (rx >= 0 && ry < 0) {
			for (int i=x1;i<=x2;i++)
				for (int j=y2;j<=y1;j++)
					if (model.getMapCheckouts().isInMap(i,j) && buffer.getRGB(i,j) == Color.white.getRGB()) {
						putInfo(mapa,id,name,type,sentido,lanes,numLane,oneway,layer,side,i,j);	
						buffer.setRGB(i,j,new Color(0,0,0).getRGB());
						pair = pair + 1;
					}
		}
		else if (rx < 0 && ry < 0) {
			for (int i=x2;i<=x1;i++)
				for (int j=y2;j<=y1;j++)
					if (model.getMapCheckouts().isInMap(i,j) && buffer.getRGB(i,j) == Color.white.getRGB()) {
						putInfo(mapa,id,name,type,sentido,lanes,numLane,oneway,layer,side,i,j);	
						buffer.setRGB(i,j,new Color(0,0,0).getRGB());
						pair = pair + 1;
					}
		}
	}
	
	public void putInfo(MapPoint[][] mapa,String id,String name,String type,Integer direction,
		Integer laneNumber,Integer lane,Boolean oneway,Integer layer,Boolean side,int i,int j) {
	
		MapPoint point = mapa[i][j];
		Boolean found = false;
		if (point.isRoad()) {
			for (int k=0;k<point.getDataList().size() && !found;k++)
				if (point.getDataList().get(k).getDirection().equals(direction) &&
					point.getDataList().get(k).getLayer().equals(layer))
					found = true;
		}
		if (!found) {
			point.setRoad(true);
			MapInfo information = new MapInfo(id,name,type,oneway,direction,layer,laneNumber,lane,pair,side);
			point.getDataList().add(information);
		}
	}

	public void generateCrossings() {

		for (int i=0;i<decoder.getNodeList().size();i++) {
			MapNode node = decoder.getNodeList().get(i);
			if (node.getInfo().containsKey(Constants.HIGHWAY)) {
				String type = node.getInfo().get(Constants.HIGHWAY);
				if (type.equals(Constants.TRAFFIC_SIGNALS) || type.equals(Constants.CROSSING)) {
					buildCrossing(node);
				}
			}
		}
	}
	
	public void buildCrossing(MapNode node) {
		
		int value = Model.Crossings.ZEBRA.ordinal();
		if (node.getInfo().containsKey(Constants.CROSSING)) {
			String crossing = node.getInfo().get(Constants.CROSSING);	
			value = getCrossingType(crossing);	
		}
		int position = 0;
		Coordinate coordinate = node.getCoordinate();
		MapPoint point = model.getMapCheckouts().getAccuratePointFromCoordinate(coordinate);
		if (point.getDataList().size() > 1) {
			if (model.getMapCheckouts().hasMoreThanOnePointPerLayerZero(point)) 
				position = model.getMapCheckouts().getCorrectCrossingPart(point);
			else
				position = model.getMapCheckouts().getLayerZeroPosition(point);	
		}
		Crossing newCrossing = new Crossing(value);
		point.setCrossing(newCrossing);
		crossings.add(point);
		//System.out.println(point.getDataList().get(0).getNumCarril()+","+point.getDataList().get(0).getCarriles());
		fillLanesWithCrossing(point,position);
	}
	
	public Boolean isExtendedCrossing(MapPoint point) {
		
		Boolean result = false;
		int type = point.getCrossing().getType();
		if (type != Model.Crossings.TRAFFICLIGHT.ordinal())
			result = true;
		return result;
	}
	
	public Boolean isCrossing(MapPoint candidate,int position,int pairValue,boolean side,boolean extended) {
		
		Boolean result = false;
		if (candidate.getCrossing() == null) {
			if (extended) {
				if (candidate.getDataList().get(position).getPair().intValue() == pairValue)
					result = true;
			}
			else {
				if (candidate.getDataList().get(position).getSide().booleanValue() == side &&
					candidate.getDataList().get(position).getPair().intValue() == pairValue) 
					result = true;
			}
		}
		return result;
	}
	
	public void fillLanesWithCrossing(MapPoint point,int position) {
		
		int x = point.x;
		int y = point.y;
		int width = point.getDataList().get(position).getLanes() * 2;
		int pairValue = point.getDataList().get(position).getPair().intValue();
		boolean side = point.getDataList().get(position).getSide().booleanValue();
		boolean isExtended = isExtendedCrossing(point);
		for (int i=x-width;i<=x+width;i++)
			for (int j=y-width;j<=y+width;j++) {
				if (model.getMapCheckouts().isInMap(i,j) && driverMap[i][j].isRoad() && 
					!model.getMapCheckouts().isSamePlace(x,y,i,j)) {
					MapPoint candidate = driverMap[i][j];
					for (int l=0;l<candidate.getDataList().size();l++) {
						if (isCrossing(candidate,l,pairValue,side,isExtended)) {
							Crossing newCrossing = new Crossing(point.getCrossing().getType());
							candidate.setCrossing(newCrossing);
							crossings.add(candidate);
						}
					}
				}
			}
		/*
		int dist = Constants.GREATEST_VALUE_INT;
		MapPoint better = point;
		int width = point.getDataList().get(position).getLanes();
		for (int i=x-width;i<=x+width;i++)
			for (int j=y-width;j<=y+width;j++) {
				if (model.getMapCheckouts().isInMap(i,j) && driverMap[i][j].isRoad() && 
					!model.getMapCheckouts().isSamePlace(x,y,i,j)) {
					MapPoint candidate = driverMap[i][j];
					Boolean found = false;
					for (int l=0;l<candidate.getDataList().size()&&!found;l++) {
						if (candidate.getDataList().get(l).getId().equals(point.getDataList().get(position).getId()) &&
								!candidate.getDataList().get(l).getLaneNumber().equals(point.getDataList().get(position).getLaneNumber())) {
								found = true;
								int rx = Math.abs(point.x - candidate.x);
								int ry = Math.abs(point.y - candidate.y);
								int possible = rx + ry;
								if (possible < dist) {
									dist = possible;
									better = candidate;
								}	
						}
					}
				}
			}
		if (dist < Constants.GREATEST_VALUE_INT) {
			Crossing newCrossing = new Crossing(point.getCrossing().getType());
			better.setCrossing(newCrossing);
			crossings.add(point);
		}*/
	}
	
	public int getCrossingType(String crossing) {
		
		int result = -1;
		if (crossing.equals(Constants.TRAFFIC_SIGNALS))
			result = Model.Crossings.TRAFFICLIGHTANDZEBRA.ordinal();
		else if (crossing.equals(Constants.UNCONTROLLED))
			result = Model.Crossings.ZEBRA.ordinal();
		else if (crossing.equals(Constants.NO))
			result = Model.Crossings.TRAFFICLIGHT.ordinal();
		else if (crossing.equals(Constants.ISLAND))
			result = Model.Crossings.ISLAND.ordinal();
		else if (crossing.equals(Constants.UNMARKED))
			result = Model.Crossings.UNMARKED.ordinal();
		return result;
	}
}
